/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
 *
 * This library is open source and may be redistributed and/or modified under
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * OpenSceneGraph Public License for more details.
*/

#ifndef OSGDB_IMAGEPAGER
#define OSGDB_IMAGEPAGER 1

#include <osg/Image>
#include <osg/NodeVisitor>
#include <osg/observer_ptr>
#include <osg/OperationThread>
#include <osg/FrameStamp>

#include <OpenThreads/Mutex>
#include <OpenThreads/Atomic>

#include <osgDB/ReaderWriter>
#include <osgDB/Options>

namespace osgDB
{

class OSGDB_EXPORT ImagePager : public osg::NodeVisitor::ImageRequestHandler
{
    public:

        ImagePager();

        class OSGDB_EXPORT ImageThread : public osg::Referenced, public OpenThreads::Thread
        {
        public:

            enum Mode
            {
                HANDLE_ALL_REQUESTS,
                HANDLE_NON_HTTP,
                HANDLE_ONLY_HTTP
            };

            ImageThread(ImagePager* pager, Mode mode, const std::string& name);

            ImageThread(const ImageThread& dt, ImagePager* pager);

            void setDone(bool done) { _done = done; }
            bool getDone() const { return _done; }

            virtual int cancel();

            virtual void run();

        protected:

            virtual ~ImageThread();

            bool                                _done;
            Mode                                _mode;
            ImagePager*                         _pager;
            std::string                         _name;
        };


        ImageThread* getImageThread(unsigned int i) { return _imageThreads[i].get(); }

        const ImageThread* getImageThread(unsigned int i) const { return _imageThreads[i].get(); }

        unsigned int getNumImageThreads() const { return static_cast<unsigned int>(_imageThreads.size()); }


        void setPreLoadTime(double preLoadTime) { _preLoadTime=preLoadTime; }
        virtual double getPreLoadTime() const { return _preLoadTime; }

        virtual osg::ref_ptr<osg::Image> readRefImageFile(const std::string& fileName, const osg::Referenced* options=0);

        virtual void requestImageFile(const std::string& fileName, osg::Object* attachmentPoint, int attachmentIndex, double timeToMergeBy, const osg::FrameStamp* framestamp, osg::ref_ptr<osg::Referenced>& imageRequest, const osg::Referenced* options);

        /** Return true if there are pending updates to the scene graph that require a call to updateSceneGraph(double). */
        virtual bool requiresUpdateSceneGraph() const;

        /** Merge the changes to the scene graph. */
        virtual void updateSceneGraph(const osg::FrameStamp &frameStamp);

        /** Signal the image thread that the update, cull and draw has begun for a new frame.
          * Note, this is called by the application so that the image pager can go to sleep while the CPU is busy on the main rendering threads. */
        virtual void signalBeginFrame(const osg::FrameStamp* framestamp);

        /** Signal the image thread that the update, cull and draw dispatch has completed.
          * Note, this is called by the application so that the image pager can go to wake back up now the main rendering threads are iddle waiting for the next frame.*/
        virtual void signalEndFrame();

        int cancel();

    protected:

        virtual ~ImagePager();
        // forward declare
        struct RequestQueue;

        struct SortFileRequestFunctor;
        friend struct SortFileRequestFunctor;

        struct ImageRequest : public osg::Referenced
        {
            ImageRequest():
                osg::Referenced(true),
                _frameNumber(0),
                _timeToMergeBy(0.0),
                _attachmentIndex(-1),
                _requestQueue(0) {}

            unsigned int                        _frameNumber;
            double                              _timeToMergeBy;
            std::string                         _fileName;
            osg::ref_ptr<Options> _loadOptions;
            osg::observer_ptr<osg::Object>      _attachmentPoint;
            int                                 _attachmentIndex;
            osg::ref_ptr<osg::Image>            _loadedImage;
            RequestQueue*                       _requestQueue;
            osg::ref_ptr<osgDB::Options>        _readOptions;

        };

        struct RequestQueue : public osg::Referenced
        {
            typedef std::vector< osg::ref_ptr<ImageRequest> > RequestList;

            void sort();

            unsigned int size() const;

            RequestList         _requestList;
            mutable OpenThreads::Mutex  _requestMutex;
        };


        struct ReadQueue : public RequestQueue
        {
            ReadQueue(ImagePager* pager, const std::string& name);

            void block() { _block->block(); }

            void release() { _block->release(); }

            void updateBlock()
            {
                _block->set((!_requestList.empty() && !_pager->_databasePagerThreadPaused));
            }

            void clear();

            void add(ImageRequest* imageRequest);

            void takeFirst(osg::ref_ptr<ImageRequest>& databaseRequest);

            osg::ref_ptr<osg::RefBlock> _block;

            ImagePager*                 _pager;
            std::string                 _name;
        };

        OpenThreads::Mutex          _run_mutex;
        bool                        _startThreadCalled;

        bool                        _done;
        bool                        _databasePagerThreadPaused;

        OpenThreads::Atomic         _frameNumber;

        OpenThreads::Mutex          _ir_mutex;
        osg::ref_ptr<ReadQueue>     _readQueue;

        typedef std::vector< osg::ref_ptr<ImageThread> > ImageThreads;
        ImageThreads                _imageThreads;

        osg::ref_ptr<RequestQueue>  _completedQueue;

        double                      _preLoadTime;
};


}

#endif

